package tcln.sors.model.core.dom;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.SequenceGenerator;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.hibernate.proxy.HibernateProxyHelper;

import tcln.commons.dataobject.CompactDescribedDataObject;
import tcln.commons.lang.ObjectUtils;
import tcln.sors.model.base.dom.AbstractSorsEntity;

/**
 * Abstract implementation of an entity specific for Hibernate ORM framework.
 * 
 * @param <ID>
 *            Identity type.
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 */
@MappedSuperclass
public abstract class AbstractHibernateEntity<ID extends Serializable> extends CompactDescribedDataObject implements
        Entity<ID> {
    private static final long serialVersionUID = 1L;

    /**
     * Unique ID of this entity.
     */
    protected ID id;

    /**
     * version number for optimistic-locking.
     */
    protected int version;

    /**
     * {@inheritDoc}
     */
    @Override
    @Column(name = "ID")
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_SORS")
    @SequenceGenerator(name = "SEQ_SORS", sequenceName = "SEQ_SORS", allocationSize = 1, initialValue = 1)
    public ID getId() {
        return id;
    }

    /**
     * Sets new value for {@link #id}.
     * 
     * @param id
     *            the new id to set
     */
    public void setId(ID id) {
        this.id = id;
    }

    /**
     * Returns current version number for optimistic-locking.
     * 
     * @return the {@link #version}.
     */
    @Version
    @Column(name = "VERSION_NUMBER")
    public int getVersion() {
        return version;
    }

    /**
     * @param version
     *            the new version to set
     * @deprecated used only by Hibernate.
     */
    @Deprecated
    public void setVersion(int version) {
        this.version = version;
    }

    /**
     * Checks if given object is an entity and is <b>persisted</b>.
     * 
     * @param object
     * @return
     */
    public static boolean isPersistedEntity(Object object) {
        boolean persisted = false;
        if (object != null) {
            Class<?> objectClass = HibernateProxyHelper.getClassWithoutInitializingProxy(object);
            if (AbstractSorsEntity.class.isAssignableFrom(objectClass)) {
                Integer entityId = ((AbstractSorsEntity) object).getId();
                persisted = entityId != null;
            }
        }
        return persisted;
    }

    /**
     * {@inheritDoc}
     */
    @Transient
    public final boolean isPersisted() {
        return isPersistedEntity(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        if (isPersistedEntity(this)) {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
            return result;
        } else {
            return super.hashCode();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else if (obj == null) {
            return false;
        } else {
            Class<?> thisClass = HibernateProxyHelper.getClassWithoutInitializingProxy(this);
            Class<?> objectClass = HibernateProxyHelper.getClassWithoutInitializingProxy(obj);
            if (thisClass != objectClass) {
                return false;
            }

            AbstractSorsEntity other = (AbstractSorsEntity) obj;
            if (isPersistedEntity(obj)) {
                return ObjectUtils.equals(getId(), other.getId());
            } else {
                return super.equals(obj);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Transient
    @Override
    protected final boolean isCopyingAllowed(Object source) {
        boolean allowed = false;
        if (source != null && (allowed = super.isCopyingAllowed(source)) == false) {
            Class<?> thisClass = HibernateProxyHelper.getClassWithoutInitializingProxy(this);
            Class<?> objectClass = HibernateProxyHelper.getClassWithoutInitializingProxy(source);
            allowed = (thisClass == objectClass);
        }
        return allowed;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void reorderDetailFieldsForToString(List<Field> fields) {
        Collections.sort(fields, new Comparator<Field>() {
            @Override
            public int compare(Field o1, Field o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });
    }
}
